package com.learn.http;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.TypeRef;
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.PropertyEditorRegistry;
import org.springframework.core.io.Resource;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

@Slf4j
public class JsonUtil {
    public static final ObjectMapper objectMapper;
    private static final Configuration conf;

    static {
        objectMapper = new ObjectMapper();
        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setDefaultPropertyInclusion(JsonInclude.Value.construct(JsonInclude.Include.ALWAYS, JsonInclude.Include.NON_NULL));
        objectMapper.configOverride(Map.class);
        objectMapper.configure(SerializationFeature.WRAP_ROOT_VALUE, false);
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        objectMapper.registerModule(new Jdk8Module());
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.addMixIn(MultipartFile.class, MultipartFileMixIn.class);
        objectMapper.addMixIn(BindingResult.class, MultipartFileMixIn.class);

        conf = Configuration.builder()
                .mappingProvider(new JacksonMappingProvider(objectMapper))
                .jsonProvider(new JacksonJsonProvider(objectMapper)).build();
    }

    abstract class MultipartFileMixIn {
        @JsonIgnore
        abstract InputStream getInputStream() throws IOException;

        @JsonIgnore
        abstract Resource getResource();

        @JsonIgnore
        abstract byte[] getBytes() throws IOException;
    }

    abstract class BindingResultMixIn {
        @JsonIgnore
        abstract Object getTarget();

        @JsonIgnore
        abstract Map<String, Object> getModel();

        @JsonIgnore
        abstract Object getRawFieldValue(String field);

        @JsonIgnore
        abstract PropertyEditorRegistry getPropertyEditorRegistry();

        @JsonIgnore
        abstract String[] getSuppressedFields();
    }

    public static String writeValueAsString(Object value) {
        if (value == null) {
            return null;
        }
        String result = null;
        try {
            result = objectMapper.writeValueAsString(value);
        } catch (Throwable e) {
            log.error("writeValueAsString error", e);
        }
        return result;
    }

    public static String writeValueAsPrettyString(Object value) {
        if (value == null) {
            return null;
        }
        String result = null;
        try {
            result = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value);
        } catch (Throwable e) {
            log.error("writeValueAsPrettyString error", e);
        }
        return result;
    }


    public static <T> T readValue(String content, Class<T> valueType) {
        if (content == null) {
            return null;
        }
        T result = null;
        try {
            result = objectMapper.readValue(content, valueType);
        } catch (Throwable e) {
            log.error("readValue error", e);
        }
        return result;
    }

    public static <T> T readValue(String content, TypeReference valueTypeRef) {
        if (content == null) {
            return null;
        }
        T result = null;
        try {
            result = (T) objectMapper.readValue(content, valueTypeRef);
        } catch (Throwable e) {
            log.error("readValue error", e);
        }
        return result;
    }

    public static <T> T readValue(String json, String jsonPath, TypeRef<T> typeRef) {
        try {
            return JsonPath.using(conf).parse(json).read(jsonPath, typeRef);
        } catch (Exception e) {
            log.error("read error", e);
        }
        return null;
    }
}
